import AttributeUtil from "@/AttributeUtil";
import DamageUtil from "@/DamageUtil";
import EventUtil from "@/EventUtil";
import RandomUtil from "@/RandomUtil";
import 事件类型 from "框架/常量/事件类型";
import 单位函数 from "框架/通用/单位";
import 数学 from "框架/通用/数学";
import 选取 from "框架/通用/选取";

export default class 伤害系统 {
  static 配置: {
    是否开启伤害浮动: boolean;
    伤害浮动幅度: number;
  } = {
    是否开启伤害浮动: true,
    伤害浮动幅度: 0.1,
  };
  constructor() {
    se.onUnitDamaged((e) => {
      if (e.damage < 1) return;

      let data: 伤害事件数据 = {
        受伤目标: e.trigUnit,
        伤害来源: e.damageSource,
        初始伤害: e.damage,
        计算后伤害: e.damage,
        最终伤害: e.damage,
        是否攻击伤害: 0 != EXGetEventDamageData(EVENT_DAMAGE_DATA_IS_ATTACK),
        是否远程伤害: 0 != EXGetEventDamageData(EVENT_DAMAGE_DATA_IS_RANGED),
        是否物理伤害: DamageUtil.isEventPhysicalDamageType(),
        是否暴击伤害: false,
        是否附加攻击: false,
      };

      伤害系统.计算伤害(data);
    });
  }

  static 计算伤害(data: 伤害事件数据) {
    // 目标死亡则不再计算
    if (GetUnitState(data.受伤目标, UNIT_STATE_LIFE) <= 0) return;
    // 单位被攻击前
    if (data.是否是攻击 || data.是否是附加攻击) {
      EventUtil.emit(事件类型.造成攻击前, data);
    }
    let 伤害来源属性 = AttributeUtil.getUnitAttribute(data.伤害来源, true);
    let 受伤目标属性 = AttributeUtil.getUnitAttribute(data.受伤目标, true);
    let 最终伤害 = data.最终伤害;

    // 伤害流程
    // 攻击增伤
    if (data.是否攻击伤害 || data.是否附加攻击) {
      最终伤害 *= 单位函数.取物理伤害增强(data.伤害来源);
      if (!data.是否分裂伤害) {
        伤害系统.造成分裂伤害(data);
      }
    }

    // 技能增伤
    if (data.是否物理伤害) {
      最终伤害 *= 单位函数.取物理伤害增强(data.伤害来源);
      // 计算暴击
      if (RandomUtil.randomPercent() <= 单位函数.取物理暴击率(data.伤害来源)) {
        最终伤害 *= 单位函数.取物理暴击伤害(data.伤害来源);
        data.是否暴击 = true;
      }
    } else {
      最终伤害 *= 单位函数.取法术伤害增强(data.伤害来源);

      // 特定法术增强
      if (data.法术伤害类型 == "光明") {
        最终伤害 += 伤害来源属性.光系伤害增强 ?? 0;
        最终伤害 *= 伤害来源属性.光系伤害增强 ?? 1;
      }
      if (data.法术伤害类型 == "冰霜") {
        最终伤害 += 伤害来源属性.冰系伤害增强 ?? 0;
        最终伤害 *= 伤害来源属性.冰系伤害增强百分比 ?? 1;
      }
      if (data.法术伤害类型 == "火焰") {
        最终伤害 += 伤害来源属性.火系伤害增强 ?? 0;
        最终伤害 *= 伤害来源属性.火系伤害增强百分比 ?? 1;
      }
      if (data.法术伤害类型 == "剧毒") {
        最终伤害 += 伤害来源属性.毒系伤害增强 ?? 0;
        最终伤害 *= 伤害来源属性.毒系伤害增强百分比 ?? 1;
      }
      if (data.法术伤害类型 == "土系") {
        最终伤害 += 伤害来源属性.土系伤害增强 ?? 0;
        最终伤害 *= 伤害来源属性.土系伤害增强百分比 ?? 1;
      }
      if (data.法术伤害类型 == "奥术") {
        最终伤害 += 伤害来源属性.奥术伤害增强 ?? 0;
        最终伤害 *= 伤害来源属性.奥术伤害增强百分比 ?? 1;
      }
      if (data.法术伤害类型 == "闪电") {
        最终伤害 += 伤害来源属性.闪电伤害增强 ?? 0;
        最终伤害 *= 伤害来源属性.闪电伤害增强百分比 ?? 1;
      }
      if (data.法术伤害类型 == "风系") {
        最终伤害 += 伤害来源属性.风系伤害增强 ?? 0;
        最终伤害 *= 伤害来源属性.风系伤害增强百分比 ?? 1;
      }
      if (data.法术伤害类型 == "黑暗") {
        最终伤害 += 伤害来源属性.暗系伤害增强 ?? 0;
        最终伤害 *= 伤害来源属性.暗系伤害增强百分比 ?? 1;
      }

      // 计算暴击
      if (RandomUtil.randomPercent() <= 单位函数.取法术暴击率(data.伤害来源)) {
        最终伤害 *= 单位函数.取法术暴击伤害(data.伤害来源);
        data.是否暴击 = true;
      }
    }

    // 暴击伤害计算
    if (data.是否暴击) {
      if (data.是否物理伤害) {
        最终伤害 *= 单位函数.取物理暴击伤害(data.伤害来源);
      } else {
        最终伤害 *= 单位函数.取法术暴击伤害(data.伤害来源);
      }
    }

    data.最终伤害 = 最终伤害;
    data.计算后伤害 = 最终伤害;
    // 派发受伤之前的事件
    EventUtil.emit(事件类型.造成伤害前, data);
    最终伤害 = data.最终伤害;

    // 计算伤害减免
    if (data.是否物理伤害) {
      let 护甲 = 单位函数.取总护甲(data.受伤目标);
      // 护甲穿透
      护甲 -= 伤害来源属性.护甲穿透 ?? 0;
      护甲 *= 1 - (伤害来源属性.护甲穿透百分比 ?? 0);
      let 护甲减免 = 数学.计算减益(护甲, ARMOR_DAMAGE_REDUCE, 1);
      最终伤害 *= 1 - 护甲减免;
      最终伤害 *=
        1 - ((受伤目标属性.物理伤害减免 ?? 0) + (受伤目标属性.全伤害减免 ?? 0));
    } else {
      let 魔抗 = 单位函数.取总魔抗(data.受伤目标);
      // 法术穿透
      魔抗 -= 伤害来源属性.法术穿透 ?? 0;
      魔抗 *= 1 - (伤害来源属性.法术穿透百分比 ?? 0);
      let 魔抗减免 = 数学.计算减益(魔抗, RESISTANCE_DAMAGE_REDUCE, 1);
      最终伤害 *= 1 - 魔抗减免;
      最终伤害 *=
        1 - ((受伤目标属性.法术伤害减免 ?? 0) + (受伤目标属性.全伤害减免 ?? 0));
      // 多种特定法术伤害减免
      if (data.法术伤害类型 == "光明") {
        最终伤害 -= 受伤目标属性.光系抗性 ?? 0;
        最终伤害 *= 1 - (受伤目标属性.光系伤害减免百分比 ?? 0);
      }
      if (data.法术伤害类型 == "冰霜") {
        最终伤害 -= 受伤目标属性.冰系抗性 ?? 0;
        最终伤害 *= 1 - (受伤目标属性.冰系伤害减免百分比 ?? 0);
      }
      if (data.法术伤害类型 == "剧毒") {
        最终伤害 -= 受伤目标属性.毒系抗性 ?? 0;
        最终伤害 *= 1 - (受伤目标属性.毒系伤害减免百分比 ?? 0);
      }
      if (data.法术伤害类型 == "土系") {
        最终伤害 -= 受伤目标属性.土系抗性 ?? 0;
        最终伤害 *= 1 - (受伤目标属性.土系伤害减免百分比 ?? 0);
      }
      if (data.法术伤害类型 == "奥术") {
        最终伤害 -= 受伤目标属性.奥术抗性 ?? 0;
        最终伤害 *= 1 - (受伤目标属性.奥术伤害减免百分比 ?? 0);
      }
      if (data.法术伤害类型 == "火焰") {
        最终伤害 -= 受伤目标属性.火系抗性 ?? 0;
        最终伤害 *= 1 - (受伤目标属性.火系伤害减免百分比 ?? 0);
      }
      if (data.法术伤害类型 == "闪电") {
        最终伤害 -= 受伤目标属性.闪电抗性 ?? 0;
        最终伤害 *= 1 - (受伤目标属性.闪电伤害减免百分比 ?? 0);
      }
      if (data.法术伤害类型 == "风系") {
        最终伤害 -= 受伤目标属性.风系抗性 ?? 0;
        最终伤害 *= 1 - (受伤目标属性.风系伤害减免百分比 ?? 0);
      }
      if (data.法术伤害类型 == "黑暗") {
        最终伤害 -= 受伤目标属性.暗系抗性 ?? 0;
        最终伤害 *= 1 - (受伤目标属性.暗系伤害减免百分比 ?? 0);
      }
    }

    // AOE
    if (data.是否AOE伤害) {
      最终伤害 *= 1 - (受伤目标属性.AOE伤害减免 ?? 0);
    }

    // 攻击伤害计算闪避
    if (data.是否攻击伤害 || data.是否附加攻击) {
      if (!data.是否无视闪避) {
        let 闪避 = 单位函数.取总闪避(data.受伤目标);
        if (RandomUtil.randomPercent() <= 闪避) {
          data.是否已经闪避 = true;
        }
      }
    }
    if (伤害系统.配置.是否开启伤害浮动) {
      最终伤害 *= RandomUtil.nextReal(
        1 - 伤害系统.配置.伤害浮动幅度,
        伤害系统.配置.伤害浮动幅度 + 1
      );
    }

    data.最终伤害 = Math.max(0, 最终伤害);

    // 发布自己的事件
    EventUtil.emit(事件类型.单位受到伤害, data);
    if (data.是否暴击伤害) {
      EventUtil.emit(事件类型.单位受到暴击, data);
    }

    // 扣血流程
    if (!data.是否已经闪避) {
      let 当前生命 = GetUnitState(data.受伤目标, UNIT_STATE_LIFE);
      SetUnitState(data.受伤目标, UNIT_STATE_LIFE, 当前生命 - data.最终伤害);
      // 计算吸血
      // 反伤分裂不吸血
      if (data.是否反射伤害 || data.是否分裂伤害) return;
      let 吸血 = 伤害来源属性.法术吸血 ?? 0;
      if (data.是否攻击伤害 || data.是否附加攻击) {
        吸血 = 伤害来源属性.物理吸血 ?? 0;
      }
      if (吸血 > 0) {
        伤害系统.造成治疗(data.伤害来源, data.伤害来源, 最终伤害 * 吸血);
      }
    } else {
      // 闪避
      EventUtil.emit(事件类型.单位闪避攻击, data);
    }
    // 造成至少0.1伤害来制造仇恨
    EXSetEventDamage(0.1);
  }

  static 造成分裂伤害(数据: 伤害事件数据, 范围 = 150) {
    if (数据.是否分裂伤害) return;
    if (数据.是否AOE伤害) return;
    if (数据.是否反射伤害) return;
    let 属性 = AttributeUtil.getUnitAttribute(数据.伤害来源, true);
    if (属性 == null) return;
    if (属性.分裂伤害系数 == null) return;
    范围 += 属性.分裂范围系数 ?? 0;
    let 分裂目标 = 选取.范围选取友军_排除中心单位(数据.受伤目标, 范围);
    if (分裂目标.length == 0) return;
    for (let item of 分裂目标) {
      let 分裂数据 = Object.assign({}, 数据);
      delete 分裂数据.受伤目标;
      delete 分裂数据.伤害来源;
      delete 分裂数据.最终伤害;
      分裂数据.是否AOE伤害 = true;
      分裂数据.是否分裂伤害 = true;
      伤害系统.造成伤害(
        item,
        数据.伤害来源,
        数据.初始伤害 * 属性.分裂伤害系数,
        分裂数据
      );
    }
  }

  static 造成伤害<T extends Partial<伤害事件数据> = Partial<伤害事件数据>>(
    目标: unit,
    来源: unit,
    伤害: number,
    选项: T
  ) {
    let 默认数据: Partial<伤害事件数据> = {
      是否攻击伤害: 0 != EXGetEventDamageData(EVENT_DAMAGE_DATA_IS_ATTACK),
      是否远程伤害: 0 != EXGetEventDamageData(EVENT_DAMAGE_DATA_IS_RANGED),
      是否物理伤害: DamageUtil.isEventPhysicalDamageType(),
      是否暴击伤害: false,
      是否附加攻击: false,
    };

    选项 = Object.assign(默认数据, 选项);

    let 伤害数据: 伤害事件数据 = {
      受伤目标: 目标,
      伤害来源: 来源,
      计算后伤害: 伤害,
      初始伤害: 伤害,
      最终伤害: 伤害,
      ...选项,
    };

    伤害系统.计算伤害(伤害数据);
  }

  static 造成治疗<T extends Partial<伤害事件数据> = Partial<伤害事件数据>>(
    目标: unit,
    来源: unit,
    数值: number,
    选项?: T,
    法强减益?: number
  ) {
    let 默认数据: Partial<伤害事件数据> = {
      是否暴击伤害: false,
    };
    选项 = Object.assign(默认数据, 选项);
    let 数据: 伤害事件数据 = {
      受伤目标: 目标,
      伤害来源: 来源,
      初始伤害: 数值,
      计算后伤害: 数值,
      最终伤害: 数值,
      ...选项,
    };
    let 法强 = 单位函数.取法强(来源);
    if (法强减益 != null) {
      法强 *= 法强减益;
    }
    if (RandomUtil.randomPercent() <= 单位函数.取法术暴击率(来源)) {
      数据.是否暴击伤害 = true;
    }
    if (数据.是否暴击伤害) {
      数值 *= 单位函数.取法术暴击伤害(来源);
    }

    数值 *= 1 + (AttributeUtil.getUnitAttribute(来源, true)?.治疗增强 ?? 0);
    数值 *= 1 + (AttributeUtil.getUnitAttribute(目标, true)?.受到治疗增强 ?? 0);

    // 加血
    let 生命 = GetUnitState(目标, UNIT_STATE_LIFE);
    生命 += 数值;
    let 最大生命 = GetUnitState(目标, UNIT_STATE_MAX_LIFE);
    if (生命 > 最大生命) {
      生命 = 最大生命;
    }
    SetUnitState(目标, UNIT_STATE_LIFE, 生命);
    数据.计算后伤害 = 数值;
    数据.最终伤害 = 数值;

    EventUtil.emit(事件类型.单位受到治疗, 数据);
  }
}
